/**
 * 广寒宫
 * 网址:www.guanghangong.xyz
 */
package com.moon.admin.flowable.service;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.flowable.bpmn.constants.BpmnXMLConstants;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.DeploymentQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.Execution;
import org.flowable.image.ProcessDiagramGenerator;
import org.flowable.ui.common.service.exception.NotFoundException;
import org.flowable.ui.modeler.domain.Model;
import org.flowable.ui.modeler.repository.ModelRepository;
import org.flowable.ui.modeler.serviceapi.ModelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Maps;
import com.moon.admin.flowable.custom.CustomProcessDiagramGenerator;
import com.moon.admin.flowable.entity.ActCnDeploymentEntity;
import com.moon.admin.flowable.entity.ActCnModelEntity;
import com.moon.admin.flowable.entity.ActGeByteArrayEntity;
import com.moon.admin.flowable.entity.FlowModelUserEntity;
import com.moon.admin.flowable.mapper.GeByteArrayMapper;
import com.moon.admin.flowable.mapper.FlowableMapper;
import com.moon.admin.framework.util.MoonException;
import com.moon.admin.framework.util.ToolUtils;

/**
 * 流程模型
 * @author ninglong
 * @date 2020年1月13日
 */
@Service
public class FlowableModelService{

	@Autowired
	private FlowableMapper flowableMapper;
	@Autowired
	private ModelService modelService;
	@Autowired
	private ModelRepository modelRepository;
	@Autowired
	private ProcessEngine processEngine;
	@Autowired
	private FlowModelUserService actFlowModelUserService;
	@Autowired
	private GeByteArrayMapper actGeByteArrayMapper;

	/**
	 * 流程模型部署
	 * @param modelId 模型id
	 * @param typeId 流程类型
	 */
	@Transactional
	public void deployModeler(String modelId,String modelKey, String typeId) {
		Model model = null;
		try{
			if(ToolUtils.isNotBlank(modelId)) {
				model = modelService.getModel(modelId);
			}else {
				model = flowableMapper.getModelByModelKey(modelKey);
				model.setKey(modelKey);
			}
		} catch (NotFoundException e) {
			throw new MoonException("失败，没有查找到ID为【" + modelId + "】的流程模板");
		}
		if (ToolUtils.isEmpty(model.getModelEditorJson())) {
			throw new MoonException("流程发布失败，【" + model.getName() + "】流程模板不完整，请完善设计");
		}
		BpmnModel bpmnModel = modelService.getBpmnModel(model);
		byte[] xmlBytes = modelService.getBpmnXML(bpmnModel);
		BufferedInputStream bin = new BufferedInputStream(new ByteArrayInputStream(xmlBytes));
		String resourceName = model.getName().replaceAll(" ", "_") + ".bpmn20.xml";
		Deployment deployment = processEngine.getRepositoryService().createDeployment()
				.name(model.getName())
				.key(model.getKey())
				.category(typeId)
				.addInputStream(resourceName, bin)
				.deploy();
		List<ProcessDefinition> list = processEngine.getRepositoryService().createProcessDefinitionQuery()
				.deploymentId(deployment.getId())
				.list();
		if(ToolUtils.isNotEmpty(list)){
			for (ProcessDefinition processDefinition : list) {
				processEngine.getRepositoryService().setProcessDefinitionCategory(processDefinition.getId(), typeId);
			}
		}
	}
	
	@Transactional
	public void removeDeployment(List<String> ids) {
		DeploymentQuery deploymentQuery = processEngine.getRepositoryService().createDeploymentQuery();
        List<Deployment> list = deploymentQuery.deploymentIds(ids).list();
		for(Deployment deploy : list){
			FlowModelUserEntity flowUser = actFlowModelUserService.getOne(Wrappers.<FlowModelUserEntity>lambdaQuery()
					.eq(FlowModelUserEntity::getModelKey, deploy.getKey()).last("limit 1"));
			if(null != flowUser){
				throw new MoonException("删除失败，流程已绑定用户不能删除");
			}
			try {
				processEngine.getRepositoryService().deleteDeployment(deploy.getId());
			}catch (Exception e) {
				throw new MoonException("删除失败，存在未完成的业务流程");
			}
			
		}
	}

	/**
	 * 根据流程key及流程实例id获取流程图
	 */
	public InputStream getModelerDiagramByModelKey(String modelKey,String processInstanceId) {
		BpmnModel bpmnModel = null;
		List<String> historicHighLightedFlowNodes = new ArrayList<>();
		List<String> runningHighLightedFlowNodes = new ArrayList<>();
		List<String> highLightedSequenceFlows = new ArrayList<>();
		//流程已部署（已发布）-查看流程图方法
		Deployment deployment = processEngine.getRepositoryService().createDeploymentQuery().deploymentKey(modelKey).latest().singleResult();
		if(null != deployment){
			ProcessDefinition process = processEngine.getRepositoryService().createProcessDefinitionQuery()
					.deploymentId(deployment.getId())
					.singleResult();
			if(null != process){
				bpmnModel = processEngine.getRepositoryService().getBpmnModel(process.getId());
			}
			// 将已经执行的历史节点ID放入高亮显示节点集合
			historicHighLightedFlowNodes = this.getHistoricHighLightedFlowNodes(processInstanceId);
			// 将正在执行的节点ID放入高亮显示节点集合
			runningHighLightedFlowNodes = this.getRunningHighLightedFlowNodes(processInstanceId);
			// 获取已流经的流程线，需要高亮显示高亮流程已发生流转的线id集合
			highLightedSequenceFlows = this.getHighLightedSequenceFlows(processInstanceId);
		}else{
			//流程待部署（待发布）-查看流程图方法
			Model model = null;
			try {
				model = modelService.getModel(modelKey);
			} catch (NotFoundException e) {
				//流程模板已删除
				throw new MoonException("失败，没有查找到model为【" + modelKey + "】的流程模板");
			}
			if (ToolUtils.isEmpty(model.getModelEditorJson())) {
				throw new MoonException("流程发布失败，【" + model.getName() + "】流程模板不完整，请完善设计");
			}
			bpmnModel = modelService.getBpmnModel(model);
		}
		if(null == bpmnModel){
			return null;
		}
		// 定义流程画布生成器
		ProcessDiagramGenerator processDiagramGenerator = new CustomProcessDiagramGenerator();
		ProcessEngineConfiguration engconf = processEngine.getProcessEngineConfiguration();
		return ((CustomProcessDiagramGenerator) processDiagramGenerator).generateDiagramCustom(bpmnModel,
				"png", historicHighLightedFlowNodes, runningHighLightedFlowNodes, highLightedSequenceFlows, engconf.getActivityFontName(),
				engconf.getLabelFontName(), engconf.getAnnotationFontName(), null, 1.0);
	}
	
	/**
	 * 通过流程实例ID获取流程中已经执行的节点，按照执行先后顺序排序
	 *
	 * @param processInstanceId
	 * @return
	 */
	private List<String> getHistoricHighLightedFlowNodes(String processInstanceId) {
		Set<String> activityTypes = new HashSet<String>();
		activityTypes.add(BpmnXMLConstants.ELEMENT_GATEWAY_EXCLUSIVE);
		activityTypes.add(BpmnXMLConstants.ELEMENT_GATEWAY_PARALLEL);
		activityTypes.add(BpmnXMLConstants.ELEMENT_GATEWAY_INCLUSIVE);
		activityTypes.add(BpmnXMLConstants.ELEMENT_EVENT_START);
		activityTypes.add(BpmnXMLConstants.ELEMENT_EVENT_END);
		activityTypes.add(BpmnXMLConstants.ELEMENT_TASK_USER);
		
		List<HistoricActivityInstance> historicActivityInstances = processEngine.getHistoryService().createHistoricActivityInstanceQuery()
			.processInstanceId(processInstanceId)
			.activityTypes(activityTypes)
			.orderByHistoricActivityInstanceStartTime()
			.asc().list();

		// 将已经执行的节点ID放入高亮显示节点集合
		List<String> highLightedFlowNodes = new ArrayList<String>();
		for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
			highLightedFlowNodes.add(historicActivityInstance.getActivityId());
		}
		return highLightedFlowNodes;
	}
	
	/**
	 * 通过流程实例ID获取流程中正在执行的节点，按照执行先后顺序排序
	 * @param processInstanceId
	 * @return
	 */
	private List<String> getRunningHighLightedFlowNodes(String processInstanceId) {
		List<Execution> executions = processEngine.getRuntimeService().createExecutionQuery().processInstanceId(processInstanceId).list();
		List<String> runningHighLightedFlowNodes = new ArrayList<String>();
		if(ToolUtils.isNotEmpty(executions)) {
			for (Execution execution : executions) {
				if (ToolUtils.isNotEmpty(execution.getActivityId())) {
					runningHighLightedFlowNodes.add(execution.getActivityId());
				}
			}
		}
		return runningHighLightedFlowNodes;
	}
	
	/**
	 * 获取已流经的流程线，需要高亮显示高亮流程已发生流转的线id集合
	 * @param processInstanceId
	 * @return
	 */
	private List<String> getHighLightedSequenceFlows(String processInstanceId){
		List<HistoricActivityInstance> activityInstances = processEngine.getHistoryService().createHistoricActivityInstanceQuery()
				.activityType(BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW).processInstanceId(processInstanceId).list();
		List<String> highLightedSequenceFlows = new ArrayList<String>();
		if(ToolUtils.isNotEmpty(activityInstances)) {
			for(HistoricActivityInstance activityInstance : activityInstances) {
				highLightedSequenceFlows.add(activityInstance.getActivityId());
			}
		}
		return highLightedSequenceFlows;
	}

	/**
	 * 待部署列表
	 */
	public IPage<ActCnModelEntity> selectModelUndeployList(ActCnModelEntity actCnModelEntity) {
		List<Model> models = null;
        if(ToolUtils.isNotEmpty(actCnModelEntity.getName())){
            models = modelRepository.findByModelTypeAndFilter(0, "%" + actCnModelEntity.getName() + "%", "modifiedDesc");
        } else {
            models = modelRepository.findByModelType(0, "modifiedDesc");
        }
        IPage<ActCnModelEntity> page = new Page<>(actCnModelEntity.getPage(),actCnModelEntity.getLimit());
        if (ToolUtils.isEmpty(models)) {
        	return page;
        }
        //已部署的流程
        DeploymentQuery deploymentQuery = processEngine.getRepositoryService().createDeploymentQuery();
        List<Deployment> list = deploymentQuery.list();
        List<String> keyList = list.stream().map(entity->entity.getKey()).collect(Collectors.toList());
        List<ActCnModelEntity> datas = new ArrayList<ActCnModelEntity>();
        for(Model model:models) {
        	if(keyList.contains(model.getKey()))continue;
        	datas.add(new ActCnModelEntity(model));
        }
        page.setTotal(datas.size());
        datas = datas.stream().sorted(Comparator.comparing(ActCnModelEntity::getLastUpdated).reversed()).skip(actCnModelEntity.getStart()).limit(actCnModelEntity.getLimit()).collect(Collectors.toList());
        page.setRecords(datas);
        return page;
	}

	/**
	 * 已部署列表
	 */
	public IPage<ActCnDeploymentEntity> selectModelDeployList(ActCnDeploymentEntity actCnDeploymentEntity) {
		DeploymentQuery deploymentQuery = processEngine.getRepositoryService().createDeploymentQuery();
		if(ToolUtils.isNotEmpty(actCnDeploymentEntity.getName())){
			deploymentQuery.deploymentNameLike("%" + actCnDeploymentEntity.getName() + "%");
		}
		if(ToolUtils.isNotEmpty(actCnDeploymentEntity.getCategory())){
			deploymentQuery.deploymentCategory(actCnDeploymentEntity.getCategory());
		}
		IPage<ActCnDeploymentEntity> page = new Page<>(actCnDeploymentEntity.getPage(),actCnDeploymentEntity.getLimit());
		List<Deployment> list = deploymentQuery.list();
		//取出相同key_category里部署时间最晚的一条数据
		Map<String,ActCnDeploymentEntity> map = Maps.newHashMap();
		for(Deployment entity:list) {
			ActCnDeploymentEntity tmp = new ActCnDeploymentEntity(entity);
			String key = tmp.getModelKey()+"_"+tmp.getCategory();
			ActCnDeploymentEntity maxDeployMent = map.get(key);
			if(maxDeployMent==null || maxDeployMent.getDeployTime().getTime()<tmp.getDeployTime().getTime()) {
				Model model = flowableMapper.getModelByModelKey(tmp.getModelKey());
				if(model.getLastUpdated().after(tmp.getDeployTime())) {
					tmp.setRepeatDeploy(true);
				}
				map.put(key, tmp);
			}
		}
		List<ActCnDeploymentEntity> records = new ArrayList<ActCnDeploymentEntity>(map.values());
		page.setTotal(records.size());
		records = records.stream().sorted(Comparator.comparing(ActCnDeploymentEntity::getDeployTime).reversed()).skip(actCnDeploymentEntity.getStart()).collect(Collectors.toList());
		page.setRecords(records);
		return page;
	}
	
	/**
	 * 获取已发布的流程模型文件
	 * @param deploymentId 流程发布id
	 * @return
	 */
	public ActGeByteArrayEntity getFlowBpmnXmlByDeploymentId(String deploymentId){
		ActGeByteArrayEntity flowByteArray = actGeByteArrayMapper.selectOne(Wrappers.<ActGeByteArrayEntity>lambdaQuery().eq(ActGeByteArrayEntity::getDeploymentId,deploymentId).likeLeft(ActGeByteArrayEntity::getName,".xml"));
		return flowByteArray;
	}
}